package bma.common.langutil.io;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * 
 * @author guanzhong
 * @since 1.0 (imported from BMA.ESP Project)
 * 
 */
public class StreamCoder {

	public static final int BOOLEAN_LENGTH = 1;

	public static final int BYTE_LENGTH = 1;

	public static final int SHORT_LENGTH = 2;

	public static final int CHAR_LENGTH = 2;

	public static final int INT_LENGTH = 4;

	public static final int FLOAT_LENGTH = INT_LENGTH;

	public static final int LONG_LENGTH = 8;

	public static final int DOUBLE_LENGTH = LONG_LENGTH;

	public static final int NULL_LENGTH = BOOLEAN_LENGTH;

	public static int writeBoolean(OutputStream out, boolean v)
			throws IOException {
		out.write(v ? 1 : 0);
		return BOOLEAN_LENGTH;
	}

	public static Boolean readBoolean(InputStream in) throws IOException {
		int v = in.read();
		if (v == -1)
			return null;
		return v != 0;
	}

	public static Boolean readBoolean(byte[] data, int off, int len)
			throws IOException {
		if (len < BOOLEAN_LENGTH)
			return null;
		return data[off + 0] != 0;
	}

	public static int writeByte(OutputStream out, byte v) throws IOException {
		out.write(v);
		return BYTE_LENGTH;
	}

	public static Byte readByte(InputStream in) throws IOException {
		int v = in.read();
		if (v == -1)
			return null;
		return (byte) (v & 0xFF);
	}

	public static Byte readByte(byte[] data, int off, int len)
			throws IOException {
		if (len < BYTE_LENGTH)
			return null;
		return data[off + 0];
	}

	public static Short readShort(InputStream in) throws IOException {
		int v1 = in.read();
		if (v1 == -1)
			return null;
		int v2 = in.read();
		if (v2 == -1)
			return null;
		return (short) (((v1 & 0xff) << 8) + (v2 & 0xff));
	}

	public static Short readShort(byte[] data, int off, int len)
			throws IOException {
		if (len < SHORT_LENGTH)
			return null;
		int v1 = data[off + 0];
		int v2 = data[off + 1];
		return (short) (((v1 & 0xff) << 8) + (v2 & 0xff));
	}

	public static int writeShort(OutputStream out, short v) throws IOException {
		out.write((byte) ((v >>> 8) & 0xFF));
		out.write((byte) ((v >>> 0) & 0xFF));
		return SHORT_LENGTH;
	}

	public static int writeChar(OutputStream out, char v) throws IOException {
		out.write((byte) ((v >>> 8) & 0xFF));
		out.write((byte) ((v >>> 0) & 0xFF));
		return CHAR_LENGTH;
	}

	public static Character readChar(InputStream in) throws IOException {
		int v1 = in.read();
		if (v1 == -1)
			return null;
		int v2 = in.read();
		if (v2 == -1)
			return null;
		return (char) (((v1 & 0xff) << 8) + (v2 & 0xff));
	}

	public static Character readChar(byte[] data, int off, int len)
			throws IOException {
		if (len < CHAR_LENGTH)
			return null;
		int v1 = data[off + 0];
		int v2 = data[off + 1];
		return (char) (((v1 & 0xff) << 8) + (v2 & 0xff));
	}

	public static int writeInt(OutputStream out, int v) throws IOException {
		out.write((byte) ((v >>> 24) & 0xFF));
		out.write((byte) ((v >>> 16) & 0xFF));
		out.write((byte) ((v >>> 8) & 0xFF));
		out.write((byte) ((v >>> 0) & 0xFF));
		return INT_LENGTH;
	}

	public static Integer readInt(InputStream in) throws IOException {
		int v1 = in.read();
		if (v1 == -1)
			return null;
		int v2 = in.read();
		if (v2 == -1)
			return null;
		int v3 = in.read();
		if (v3 == -1)
			return null;
		int v4 = in.read();
		if (v4 == -1)
			return null;
		return (int) (((v1 & 0xff) << 24) + ((v2 & 0xff) << 16)
				+ ((v3 & 0xff) << 8) + ((v4 & 0xff)));
	}

	public static Integer readInt(byte[] data, int off, int len)
			throws IOException {
		if (len < INT_LENGTH)
			return null;
		int v1 = data[off + 0];
		int v2 = data[off + 1];
		int v3 = data[off + 2];
		int v4 = data[off + 3];
		return (int) (((v1 & 0xff) << 24) + ((v2 & 0xff) << 16)
				+ ((v3 & 0xff) << 8) + ((v4 & 0xff)));
	}

	public static int writeVarInt(OutputStream out, int v) throws IOException {
		if (v >= 0) {
			if (v < 0x00FF) {
				out.write((byte) (v & 0xFF));
				return 1;
			}
			if (v < 0x00FFFF) {
				out.write(0xFF);
				out.write((byte) ((v >>> 8) & 0xFF));
				out.write((byte) ((v >>> 0) & 0xFF));
				return 3;
			}
		}

		out.write(0xFF);
		out.write(0xFF);
		out.write(0xFF);
		return writeInt(out, v) + 3;
	}

	public static Integer readVarInt(InputStream in) throws IOException {
		int v0 = in.read();
		if (v0 == -1)
			return null;
		if (v0 != 0xFF) {
			return v0;
		}
		int v1 = in.read();
		if (v1 == -1)
			return null;
		int v2 = in.read();
		if (v2 == -1)
			return null;

		if (!(v1 == 0xFF && v2 == 0xFF)) {
			return (int) (((v1 & 0xff) << 8) + (v2 & 0xff));
		}

		return readInt(in);
	}

	public static int writeLong(OutputStream out, long v) throws IOException {
		out.write((byte) ((v >>> 56) & 0xFF));
		out.write((byte) ((v >>> 48) & 0xFF));
		out.write((byte) ((v >>> 40) & 0xFF));
		out.write((byte) ((v >>> 32) & 0xFF));
		out.write((byte) ((v >>> 24) & 0xFF));
		out.write((byte) ((v >>> 16) & 0xFF));
		out.write((byte) ((v >>> 8) & 0xFF));
		out.write((byte) ((v >>> 0) & 0xFF));
		return LONG_LENGTH;
	}

	public static Long readLong(InputStream in) throws IOException {
		long v1 = in.read();
		if (v1 == -1)
			return null;
		long v2 = in.read();
		if (v2 == -1)
			return null;
		long v3 = in.read();
		if (v3 == -1)
			return null;
		long v4 = in.read();
		if (v4 == -1)
			return null;
		long v5 = in.read();
		if (v5 == -1)
			return null;
		long v6 = in.read();
		if (v6 == -1)
			return null;
		long v7 = in.read();
		if (v7 == -1)
			return null;
		long v8 = in.read();
		if (v8 == -1)
			return null;
		return (long) (((v1 & 0xff) << 56) + ((v2 & 0xff) << 48)
				+ ((v3 & 0xff) << 40) + ((v4 & 0xff) << 32)
				+ ((v5 & 0xff) << 24) + ((v6 & 0xff) << 16)
				+ ((v7 & 0xff) << 8) + ((v8 & 0xff)));
	}

	public static Long readLong(byte[] data, int off, int len)
			throws IOException {
		if (len < LONG_LENGTH)
			return null;
		long v1 = data[off + 0];
		long v2 = data[off + 1];
		long v3 = data[off + 2];
		long v4 = data[off + 3];
		long v5 = data[off + 4];
		long v6 = data[off + 5];
		long v7 = data[off + 6];
		long v8 = data[off + 7];
		return (long) (((v1 & 0xff) << 56) + ((v2 & 0xff) << 48)
				+ ((v3 & 0xff) << 40) + ((v4 & 0xff) << 32)
				+ ((v5 & 0xff) << 24) + ((v6 & 0xff) << 16)
				+ ((v7 & 0xff) << 8) + ((v8 & 0xff)));
	}

	public static int writeFloat(OutputStream out, float v) throws IOException {
		return writeInt(out, Float.floatToIntBits(v));
	}

	public static Float readFloat(InputStream in) throws IOException {
		Integer v = readInt(in);
		if (v == null)
			return null;
		return Float.intBitsToFloat(v);
	}

	public static Float readFloat(byte[] data, int off, int len)
			throws IOException {
		Integer v = readInt(data, off, len);
		if (v == null)
			return null;
		return Float.intBitsToFloat(v);
	}

	public static int writeDouble(OutputStream out, double v)
			throws IOException {
		return writeLong(out, Double.doubleToLongBits(v));
	}

	public static Double readDouble(InputStream in) throws IOException {
		Long v = readLong(in);
		if (v == null)
			return null;
		return Double.longBitsToDouble(v);
	}

	public static Double readDouble(byte[] data, int off, int len)
			throws IOException {
		Long v = readLong(data, off, len);
		if (v == null)
			return null;
		return Double.longBitsToDouble(v);
	}

	public static int writeNull(OutputStream out, Object obj)
			throws IOException {
		writeBoolean(out, obj == null);
		return BOOLEAN_LENGTH;
	}

	public static Boolean readNull(InputStream in) throws IOException {
		return readBoolean(in);
	}

	public static Boolean readNull(byte[] data, int off, int len)
			throws IOException {
		return readBoolean(data, off, len);
	}

	public static int writeString(OutputStream out, String s)
			throws IOException {
		int r = writeBoolean(out, s != null);
		if (s != null) {
			byte[] buf = s.getBytes("UTF-8");
			writeVarInt(out, buf.length);
			out.write(buf);
		}
		return r;
	}

	public static String readString(InputStream in) throws IOException {
		Boolean b = readBoolean(in);
		if (b == null)
			return null;
		if (!b.booleanValue())
			return null;
		Integer len = readVarInt(in);
		if (len == null)
			return null;
		if (len == 0)
			return "";
		byte[] buf = new byte[len];
		if (in.read(buf) != len)
			return null;
		return new String(buf, "UTF-8");
	}
}
